package net.sf.midollo.io.text;

import java.io.IOException;
import java.io.InputStream;

public class Parser {

	public static final int STRING_BUFFER_LENGTH_MAX = 16 * 1024;
	public static final int MAX_INT_DIGITS = 9;
	public static final int MAX_LONG_DIGITS = 19;

	protected final InputStream is;
	protected boolean closeOnError;
	protected boolean currentBytePending;
	protected int currentByte;

	public Parser(InputStream is) throws IOException {
		this.is = is;
		this.closeOnError = true;
		this.currentBytePending = false;
		this.currentByte = -1;
	}

	public boolean isCloseOnError() {
		return this.closeOnError;
	}

	public void setCloseOnError(boolean closeOnError) {
		this.closeOnError = closeOnError;
	}

	public void parseString(final StringBuffer buffer, final int delimiter) throws IOException {
		final int max = STRING_BUFFER_LENGTH_MAX;
		int count = max;
		int c;
		buffer.setLength(0);
		while (true) {
			c = peek();
			if (delimiter == c || c == '\r' || c == '\n')
				return;
			if (--count < 0)
				throwError("no more than " + max + " chars allowed");
			buffer.append((char)c);
			pop();
		}
	}

	public int parseInt() throws IOException {
		int count = MAX_INT_DIGITS;
		int value = 0;
		int digit;
		int c;
		while (true) {
			c = peek();
			digit = Character.digit((char)c, 10);
			if (digit < 0)
				return value;
			if (--count < 0)
				throwError("no more than " + MAX_INT_DIGITS + " digits allowed");
			value = value * 10 + digit;
			pop();
		}
	}

	public long parseLong() throws IOException {
		int count = MAX_LONG_DIGITS;
		long value = 0L;
		int digit;
		int c;
		while (true) {
			c = peek();
			digit = Character.digit((char)c, 10);
			if (digit < 0)
				return value;
			if (--count < 0)
				throwError("no more than " + MAX_LONG_DIGITS + " digits allowed");
			value = value * 10L + digit;
			pop();
		}
	}

	public void parseOptionalSpaces(final int max) throws IOException {
		int count = max;
		int c;
		while (true) {
			c = peek();
			if (c != ' ')
				return;
			if (--count < 0)
				throwError("no more than " + max + " spaces allowed");
			pop();
		}
	}

	public void parseByte(int value) throws IOException {
		int c = peek();
		if (c != value)
			throwError("received: " + charDesc(c) + " expected: " + charDesc(value));
		pop();
	}

	public void parseSpace() throws IOException {
		int c = peek();
		if (c != ' ')
			throwError("received: " + charDesc(c) + " expected: SP");
		pop();
	}

	public void parseCRLF() throws IOException {
		int c = peek();
		if (c == '\n') {
			pop();
		} else if (c == '\r') {
			pop();
			c = peek();
			if (c == '\n')
				pop();
		} else {
			throwError("received: " + charDesc(c) + " expected: CR or LF");
		}
	}

	public int peek() throws IOException {
		if (!this.currentBytePending) {
			this.currentByte = this.is.read();
			this.currentBytePending = true;
		}
		return this.currentByte;
	}

	public void pop() throws IOException {
		if (!this.currentBytePending)
			throwError("illegal state");
		this.currentBytePending = false;
		this.currentByte = -1;
	}

	public void throwError(String msg) throws IOException {
		throwError(new IOException("Parser error: " + msg));
	}

	public void throwError(IOException error) throws IOException {
		if (this.closeOnError) {
			try {
				this.is.close();
			} catch (Exception e) {
			}
		}
		throw error;
	}

	public static String charDesc(int c) {
		return c + " '" + (char)c + "'";
	}
}
